import { ISA, type AirData } from '@/atmosphere'
import { getEntity } from '../base'
import type { AircraftType, IASItem } from './types'

export interface FlightPerformance {
  altitude: number
  minClimbIAS: number
  normClimbIAS: number
  maxClimbIAS: number
  climbFuel: number
  minCruiseIAS: number
  normCruiseIAS: number
  maxCruiseIAS: number
  cruiseFuel: number
  minDescentIAS: number
  normDescentIAS: number
  maxDescentIAS: number
  descentFuel: number
  normClimbRate: number
  maxClimbRate: number
  normDescentRate: number
  maxDescentRate: number
  normTurnRate: number
  maxTurnRate: number
}

export class FlightPerformanceTable {
  readonly altStep = ALT_STEP
  readonly data: FlightPerformance[]

  constructor(acType: AircraftType) {
    const data = initFlightPerformanceList()
    calcIASFlightPerformance(acType, 'Climb', data)
    calcIASFlightPerformance(acType, 'Cruise', data)
    calcIASFlightPerformance(acType, 'Descent', data)
    calcRateFlightPerformance(acType, 'Climb', data)
    calcRateFlightPerformance(acType, 'Descent', data)
    calcRateFlightPerformance(acType, 'Turn', data)
    this.data = data
  }

  public getPerformance(alt: number): FlightPerformance {
    return calcFlightPerformance(this.data, alt)
  }
}

const tables: Record<string, FlightPerformanceTable> = {}

export function getFlightPerformanceTableById(id: string): FlightPerformanceTable | null {
  const acType = getEntity<AircraftType>('AircraftType', id)
  if (!acType) {
    return null
  }
  return getFlightPerformanceTable(acType)
}

export function getFlightPerformanceTable(acType: AircraftType): FlightPerformanceTable {
  const id = acType.id
  let f = tables[id]
  if (!f) {
    f = new FlightPerformanceTable(acType)
    tables[id] = f
  }
  return f
}
const ALT_STEP = 100
const MAX_ALT = ALT_STEP * 120

function initFlightPerformanceList(): FlightPerformance[] {
  const data = new Array<FlightPerformance>(MAX_ALT / ALT_STEP)
  for (let idx = 0; idx < data.length; idx++) {
    data[idx] = {
      altitude: idx * ALT_STEP,
      minClimbIAS: 0,
      normClimbIAS: 0,
      maxClimbIAS: 0,
      climbFuel: 0,
      minCruiseIAS: 0,
      normCruiseIAS: 0,
      maxCruiseIAS: 0,
      cruiseFuel: 0,
      minDescentIAS: 0,
      normDescentIAS: 0,
      maxDescentIAS: 0,
      descentFuel: 0,
      normClimbRate: 0,
      maxClimbRate: 0,
      normDescentRate: 0,
      maxDescentRate: 0,
      normTurnRate: 0,
      maxTurnRate: 0
    }
  }
  return data
}

function calcIASFlightPerformance(
  acType: AircraftType,
  type: 'Climb' | 'Cruise' | 'Descent',
  data: FlightPerformance[]
) {
  let iasLst: IASItem[]
  switch (type) {
    case 'Climb': {
      iasLst = fixIASList(acType.climbIASList)
      break
    }
    case 'Cruise': {
      iasLst = fixIASList(acType.cruiseIASList)
      break
    }
    default: {
      iasLst = fixIASList(acType.descentIASList)
    }
  }

  let itemIdx = 0
  let preItem = iasLst[0]
  let nextItem = iasLst[0]
  const lastItem = iasLst[iasLst.length - 1]
  const minKey = `min${type}IAS` as 'minClimbIAS' | 'minCruiseIAS' | 'minDescentIAS'
  const normKey = `norm${type}IAS` as 'normClimbIAS' | 'normCruiseIAS' | 'normDescentIAS'
  const maxKey = `max${type}IAS` as 'maxClimbIAS' | 'maxCruiseIAS' | 'maxDescentIAS'
  const fuelKey = `${type}Fuel` as 'climbFuel' | 'cruiseFuel' | 'descentFuel'

  for (const e of data) {
    const alt = e.altitude
    if (alt > nextItem.altitude) {
      itemIdx++
      if (itemIdx >= iasLst.length) {
        preItem = lastItem
        nextItem = lastItem
      } else {
        preItem = iasLst[itemIdx - 1]
        nextItem = iasLst[itemIdx]
      }
    }
    if (preItem === nextItem || preItem.altitude === nextItem.altitude) {
      e[minKey] = preItem.min
      e[normKey] = preItem.norm
      e[maxKey] = preItem.max
      e[fuelKey] = preItem.fuel
    } else {
      const f = (alt - preItem.altitude) / (nextItem.altitude - preItem.altitude)
      e[minKey] = f * (nextItem.min - preItem.min) + preItem.min
      e[normKey] = f * (nextItem.norm - preItem.norm) + preItem.norm
      e[maxKey] = f * (nextItem.max - preItem.max) + preItem.max
      e[fuelKey] = f * (nextItem.fuel - preItem.fuel) + preItem.fuel
    }
  }
}

function calcRateFlightPerformance(
  acType: AircraftType,
  type: 'Climb' | 'Turn' | 'Descent',
  data: FlightPerformance[]
) {
  const rateList =
    type === 'Climb'
      ? acType.climbRateList
      : type === 'Descent'
      ? acType.descendRateList
      : acType.turnRateList

  let itemIdx = 0
  let preItem = rateList[0]
  let nextItem = rateList[0]
  const lastItem = rateList[rateList.length - 1]
  const normKey = `norm${type}Rate` as 'normClimbRate' | 'normTurnRate' | 'normDescentRate'
  const maxKey = `max${type}Rate` as 'maxClimbRate' | 'maxTurnRate' | 'maxDescentRate'

  for (const e of data) {
    const alt = e.altitude
    if (alt > nextItem.altitude) {
      itemIdx++
      if (itemIdx >= rateList.length) {
        preItem = lastItem
        nextItem = lastItem
      } else {
        preItem = rateList[itemIdx - 1]
        nextItem = rateList[itemIdx]
      }
    }
    if (preItem === nextItem) {
      e[normKey] = preItem.norm
      e[maxKey] = preItem.max
    } else {
      const f = (alt - preItem.altitude) / (nextItem.altitude - preItem.altitude)
      e[normKey] = f * (nextItem.norm - preItem.norm) + preItem.norm
      e[maxKey] = f * (nextItem.max - preItem.max) + preItem.max
    }
  }
}
function fixIASList(lst: IASItem[]): IASItem[] {
  const ret: IASItem[] = []
  for (const e1 of lst) {
    if (e1.min && e1.norm && e1.max) {
      ret.push(e1)
      continue
    }
    const e = Object.assign({}, e1)
    let ad: AirData | undefined
    if (!e.min) {
      if (!ad) {
        ad = ISA.getAirData(e.altitude)
      }
      e.min = ad.mach2CAS(e.minMach)
    }
    if (!e.norm) {
      if (!ad) {
        ad = ISA.getAirData(e.altitude)
      }
      e.norm = ad.mach2CAS(e.normMach)
    }
    if (!e.max) {
      if (!ad) {
        ad = ISA.getAirData(e.altitude)
      }
      e.max = ad.mach2CAS(e.maxMach)
    }
    ret.push(e)
  }
  return ret
}

function calcFlightPerformance(data: FlightPerformance[], alt: number): FlightPerformance {
  const i0 = Math.floor(alt / ALT_STEP)
  if (alt === i0 * ALT_STEP || i0 >= data.length - 1) {
    let idx = i0
    if (idx < 0) {
      idx = 0
    } else if (idx >= data.length) {
      idx = data.length - 1
    }
    return Object.assign({ altitude: alt }, data[idx])
  }
  const pre = data[i0]
  const next = data[i0 + 1]
  const f = (alt - pre.altitude) / (next.altitude - pre.altitude)
  return {
    altitude: alt,
    minClimbIAS: f * (next.minClimbIAS - pre.minClimbIAS) + pre.minClimbIAS,
    normClimbIAS: f * (next.normClimbIAS - pre.normClimbIAS) + pre.normClimbIAS,
    maxClimbIAS: f * (next.maxClimbIAS - pre.maxClimbIAS) + pre.maxClimbIAS,
    climbFuel: f * (next.climbFuel - pre.climbFuel) + pre.climbFuel,
    minCruiseIAS: f * (next.minCruiseIAS - pre.minCruiseIAS) + pre.minCruiseIAS,
    normCruiseIAS: f * (next.normCruiseIAS - pre.normCruiseIAS) + pre.normCruiseIAS,
    maxCruiseIAS: f * (next.maxCruiseIAS - pre.maxCruiseIAS) + pre.maxCruiseIAS,
    cruiseFuel: f * (next.cruiseFuel - pre.cruiseFuel) + pre.cruiseFuel,
    minDescentIAS: f * (next.minDescentIAS - pre.minDescentIAS) + pre.minDescentIAS,
    normDescentIAS: f * (next.normDescentIAS - pre.normDescentIAS) + pre.normDescentIAS,
    maxDescentIAS: f * (next.maxDescentIAS - pre.maxDescentIAS) + pre.maxDescentIAS,
    descentFuel: f * (next.descentFuel - pre.descentFuel) + pre.descentFuel,
    normClimbRate: f * (next.normClimbRate - pre.normClimbRate) + pre.normClimbRate,
    maxClimbRate: f * (next.maxClimbRate - pre.maxClimbRate) + pre.maxClimbRate,
    normDescentRate: f * (next.normDescentRate - pre.normDescentRate) + pre.normDescentRate,
    maxDescentRate: f * (next.maxDescentRate - pre.maxDescentRate) + pre.maxDescentRate,
    normTurnRate: f * (next.normTurnRate - pre.normTurnRate) + pre.normTurnRate,
    maxTurnRate: f * (next.maxTurnRate - pre.maxTurnRate) + pre.maxTurnRate
  }
}
